package com.winit.core.server.linux;

import com.jcraft.jsch.Session;
import com.winit.common.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;


/**
 * 抽象一台可以提供Portforwarding服务的跳板机服务器.
 * <p>
 * Created by keel2008 on 16/8/9.
 */
public class LinuxJumpSSHServer extends LinuxSSHServer {

    private static final Logger logger = LoggerFactory.getLogger(LinuxJumpSSHServer.class);

    // 缓存已经创建了的port forwarding端口.
    Map<String, PFLPortService> portForwardingCache = new HashMap<String, PFLPortService>();

    //创建跳板机自己的连接信息.
    public LinuxJumpSSHServer(String ip, String userName, Integer port) throws Exception {
        super(ip, userName, port);
    }

    /**
     * 将目标服务器包装成基于跳板机的服务器.(实际是将内部的session指向了PF服务的端口)
     *
     * @param source
     * @return
     * @throws Exception
     */
    public LinuxSSHServer jump(LinuxSSHServer source) throws Exception {

        Assert.notNull(source);

        if (!session.isConnected()) {
            this.connect();
        }

        //1.创建之前,要先断开原来的Server连接.
        source.disConnect();

        //2.获取这个服务器想要连接的目标信息.
        final LoginInfo sourceLoginInfo = source.getLoginInfo();

        //3.利用目标信息,结合跳板机提供的跳板服务,修改原对象的session,让其关联跳板机的跳板服务.
        source.session = getJumpedSSHServerSession(sourceLoginInfo);

        //4.打开连接.
        source.connect();

        logger.info("[[Server]]通过SSH跳板机登陆:"+source.loginInfo.ip+" port:"+source.loginInfo.port);
        logger.info("[[Server]]跳板机服务端口:"+session.getPort());

        return source;
    }

    /**
     * 创建以PF为基础的回话对象,该对象直接
     * 注意创建好的对象并未连接.
     *
     * @param targetLoginInfo
     * @return
     * @throws Exception
     */
    private Session getJumpedSSHServerSession(LoginInfo targetLoginInfo) throws Exception {

        Assert.notNull(targetLoginInfo);
        Assert.hasText(targetLoginInfo.ip);
        Assert.isTrue(session.isConnected());

        //尝试获取已经创建的服务.
        PFLPortService js = this.getPFLPortService(targetLoginInfo);

        return this.jsch.getSession(targetLoginInfo.userName, "127.0.0.1", js.port);

    }

    /**
     * 获取或创建跳板机提供的Portforwarding的服务信息,端口号.
     *
     * @param targetLoginInfo
     * @return
     * @throws Exception
     */
    private PFLPortService getPFLPortService(LoginInfo targetLoginInfo) throws Exception {

        //1.尝试获取已经创建过的PF端口.
        PFLPortService js = this.portForwardingCache.get(targetLoginInfo.ip);

        //1.1 如果能获取,直接返回之.
        if (js != null && this.isPortUsing(js.port))
            return js;

        this.portForwardingCache.remove(targetLoginInfo.ip);

        //2.获取不到的情况下,以目标机的信息创建一个PF服务端口,启动这个县城.
        Integer newLPort = session.setPortForwardingL(0, targetLoginInfo.ip, targetLoginInfo.port);

        System.out.println(DateUtil.getCurDateStr(DateUtil.FORMAT_SS)+newLPort);

        //3.将
        this.portForwardingCache.put(targetLoginInfo.ip, new PFLPortService(newLPort, System.currentTimeMillis()));

        return new PFLPortService(newLPort, System.currentTimeMillis());

    }

    /**
     * 尝试去连接这台服务器,5秒连不上算超时
     *
     * @param
     * @return
     */
    protected Boolean isAvailable(Session source,LoginInfo targetLoginInfo) {
        Boolean success = false;
        try {
//            doConnect(source,targetLoginInfo,connectionTimeout);
//            source.disconnect();
            success = true;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }

        return success;
    }

    /**
     * 抽象本地PF端口的对象,用于缓存.
     */
    public class PFLPortService {
        public Integer port;
        public Long createTime;

        public PFLPortService(Integer port, Long createTime) {
            this.port = port;
            this.createTime = createTime;
        }
    }

    public static boolean isPortUsing(int port) throws Exception {

        Socket s = null;
        try {
            InetAddress theAddress = InetAddress.getByName("127.0.0.1");
            s = new Socket(theAddress, port);

        }catch (Exception e){
            return false;
        }finally {
            try {
                if (s != null)
                    s.close();
            } catch (IOException e) {
                e.printStackTrace();
                logger.error(e.getMessage(),e);
            }
        }

        return true;
    }

    @Override
    public String toString(){

        StringBuilder sb = new StringBuilder();

        for (String key:
             this.portForwardingCache.keySet()) {
            PFLPortService ps = this.portForwardingCache.get(key);
            sb.append(ps.port).append(" ");
        }

        return sb.toString();
    }
}
